[Previous] [Next]

The RichTextBox Control

The RichTextBox control is one of the most powerful controls provided with Visual Basic. In a nutshell, it's a text box that's able to display text stored in Rich Text Format (RTF), a standard format recognized by virtually all word processors, including Microsoft WordPad (not surprisingly, since WordPad internally uses the RichTextBox control). This control supports multiple fonts and colors, left and right margins, bulleted lists, and more.

You might need time to get used to the many features of the RichTextBox control. The good news is that the RichTextBox control is code-compatible with a regular multiline TextBox control, so you can often recycle code that you have written for a TextBox control. But unlike the standard TextBox control, the RichTextBox control has no practical limit to the number of lines of text it can contain.

The RichTextBox control is embedded in the RichTx32.ocx file, which you must distribute with all the applications that use this control.

Setting Design-Time Properties

You can set a few useful design-time properties in the General tab of the Property Pages dialog box as you can see in Figure 12-8. For example, you can type the name of a TXT or RTF file that must be loaded when the form is loaded and that corresponds to the Filename property.

The RightMargin property represents the distance in twips of the right margin from the left border of the control. The BulletIndent is the number of twips a paragraph is indented when the SetBullet property is True. The AutoVerbMenu is an interesting property that lets you prevent the standard Edit pop-up menu from appearing when the user right-clicks on the control. If you want to display your own pop-up menu, leave this property as False. All the other properties in this General page are also supported by standard TextBox controls, so I won't describe them here.

In the Appearance tab of the Properties dialog box, you find other properties, such as BorderStyle and ScrollBars, whose meaning should already be known to you. An exception is the DisableNoScroll property: When the ScrollBars property is assigned a value other than 0-rtfNone and you set the DisableNoScroll property to True, the RichTextBox control will always display the scroll bars, even if the current document is so short that it doesn't require scrolling. This is consistent with the behavior of most word processors.

The RichTextBox control is data-aware and therefore exposes the usual Dataxxxx properties that let you bind the control to a data source. In other words, you can write entire TXT or RTF documents in a single field of a database.

Click to view at full size.

Figure 12-8. The General tab of the Properties dialog box of a RichTextBox control.

Run-Time Operations

The RichTextBox control exposes so many properties and methods that it makes sense to subdivide them in groups, according to the action you want to perform.

Loading and saving files

You can load a text file into the control using the LoadFile method, which expects the filename and an optional argument that specifies whether the file is in RTF format (0-rtfRTF, the default) or plain text (1-rtfText):

' Load an RTF file into the control.
RichTextBox1.LoadFile "c:\Docs\TryMe.Rtf", rtfRTF

The name of the file loaded by this method becomes available in the FileName property. You can also indirectly load a file into the control by assigning its name to the FileName property, but in this case you have no way of specifying the format.

You can save the current contents of the control using the SaveFile method, which has a similar syntax:

' Save the text back into the RTF file.
RichTextBox1.SaveFile RichTextBox1.FileName, rtfRTF

The LoadFile and SaveFile methods are a good solution when you want to load or save the entire contents of a file. At times, however, you might want to append the contents of the control to an existing file or store multiple portions of text in the same file. In such cases, you can use the TextRTF property with regular Visual Basic file commands and functions:

' Store the RTF text from two RichtextBox controls in the same file.
Dim tmp As Variant
Open "c:\tryme.rtf" For Binary As #1
' Use an intermediate Variant variable to ease the process.
' (Don't need to store the length of each piece of data.)
tmp = RichTextBox1.TextRTF: Put #1, , tmp
tmp = RichTextBox2.Text RTF: Put #1, , tmp
Close #1

' Read the data back in the two controls.
Open "c:\tryme.rtf" For Binary As #1
Get #1, , tmp: RichTextBox1.TextRTF = tmp
Get #1, , tmp: RichTextBox2.TextRTF = tmp
Close #1

You can use this technique to save and reload the entire contents of the control in plain or RTF format (using the Text and TextRTF properties), and you can even save and reload just the text that's currently selected (using the SelText and SelRTF properties).

Changing character attributes

The RichTextBox control exposes many properties that affect the attributes of the characters in the selected text: These are SelFontName, SelFontSize, SelColor, SelBold, SelItalic, SelUnderline, and SelStrikeThru. Their names are self-explanatory, so I won't describe what each one does. You might find it interesting to note that all of the properties work as they would within a regular word processor. If text is currently selected, the properties set or return the corresponding attributes; if no text is currently selected, they set or return the attributes that are active from the insertion point onward.

The control also exposes a Font property and all the various Fontxxxx properties, but these properties affect the attributes only when the control is loaded. If you want to change the attribute of the entire document, you must select the whole document first:

' Change font name and size of entire contents.
RichTextBox1.SelStart = 0 
RichTextBox1.SelLength = Len(RichTextBox1.Text)
RichTextBox1.SelFontName = "Times New Roman"
RichTextBox1.SelFontSize = 12
' Cancel the selection.
RichTextBox1.SelLength = 0

When you read the value of the Selxxxx properties, you see that they return the attributes of the selected text but can also return Null if the selection includes characters with different attributes. This means that you must take precautions when toggling the attributes of the selected text:

Private Sub cmdToggleBold_Click()
    If IsNull(RichTextBox1.SelBold) Then
        ' Test for Null values first to avoid errors later.
        RichTextBox1.SelBold = True
    Else
        ' If not Null, we can toggle the Boolean value using
        ' the Not operator.
        RichTextBox1.SelBold = Not RichTextBox1.SelBold
    End If
End Sub

A similar problem occurs when your application includes a toolbar whose buttons reflect the Bold, Italic, Underline, and other attributes of the selection. In this case, you need to use the MixedState property of the toolbar's Button objects and also exploit the fact that when the user selects or deselects text, the RichTextBox control fires a SelChange event:

Private Sub RichTextBox1_SelChange()
    ' Keep the toolbar's button in sync with current selection.
    If IsNull(RichTextBox1.SelBold) Then
        ToolBar1.Buttons("Bold").MixedState = True
    Else
        ToolBar1.Buttons("Bold").MixedState = False
        ToolBar1.Buttons("Bold").Value = IIf(rtfText.SelBold, _
            tbrPressed, tbrUnpressed)
    End If
    ' Add similar code that deals with Italic, Underline, and so on.
    ' ...
End Sub

The demonstration program shown in Figure 12-9 uses this technique. I built the skeleton of this program using the Application Wizard, but I had to manually edit the code generated by the wizard to account for the fact that many Selxxxx properties can return Null values. I also included a CoolBar that hosts a transparent Toolbar control, using the technique described in Chapter 11.

Click to view at full size.

Figure 12-9. The demonstration program is an MDI mini-word processor.

SelProtect is an interesting property that lets you protect the selected text from being edited. Use it when the document includes crucial data that you don't want the user to accidentally delete or modify. If you want nothing in the entire document to be modified, however, you'd better set the Locked property to True.

Changing paragraph attributes

You can control the formatting of all the paragraphs that are included in the current selection. The SelIndent and SelHangingIndent properties work together to define the left indentation of the first line and all the following lines of a paragraph. The way these properties work differs from how word processors usually define these sorts of entities: The SelIndent property is the distance (in twips) of the first line of the paragraph from the left border, whereas the SelHangingIndent property is the indentation of all the following lines relative to the indentation of the first line. For example, this is the code that you must execute to have a paragraph that is indented by 400 twips and whose first line is indented by an additional 200 twips:

RichTextBox1.SelIndent = 600   ' Left indentation + 1st line indentation
RichTextBox1.SelHangingIndent = -200   ' A negative value

The SelRightIndent property is the distance of the paragraph from the right margin of the document (whose position depends on the RightMargin property). The following code moves the right margin about 300 twips from the right border of the control, and then sets a right indentation of 100 twips for the paragraphs that are currently selected:

' RightMargin is measured from the left border.
RichTextBox1.RightMargin = RichTextBox1.Width _ 300
RichTextBox1.SelRightIndent = 100

You can control the alignment of a paragraph by means of the SelAlignment enumerated property, which can be assigned the values 0-rtfLeft, 1-rtfRight, or 2-rtfCenter. (The RichTextBox control doesn't support justified paragraphs.) You can read this property to retrieve the alignment state of all the paragraphs in the selection: In this case, the property returns Null if the paragraphs have different alignments.

The SelCharOffset property lets you create superscript and subscript text—in other words, position characters slightly above or below the text baseline. A positive value for this property creates a superscript, a negative value creates a subscript, and a zero value restores the regular text position. You shouldn't assign this property large positive or negative values, though, because they would make the superscript or subscript text unreadable (or even invisible)—the RichTextBox control doesn't automatically adjust the distance between lines if they contain superscript or subscript text:

' Make the selection superscript text.
RichTextBox1.SelCharOffset = 40
' Don't forget to reduce the characters' size.
RichTextBox1.SelFontSize = RichTextBox1.SelFontSize \ 2

The SelBullet Boolean property can be set to True to morph a normal paragraph into a bulleted paragraph. It returns the attribute of the paragraphs currently selected or Null if the selection includes paragraphs with different attributes:

' Toggle the bullet attribute of the selected paragraphs.
Private Sub cmdToggleBullet_Click()
    If IsNull(RichTextBox1.SelBullet) Then
        RichTextBox1.SelBullet = True
    Else
        RichTextBox1.SelBullet = Not RichTextBox1.SelBullet
    End If
End Sub

You can control the distance between the bullet and the paragraph body by using the BulletIndent property, which affects the entire document.

Managing the Tab key

Like a real word processor, the RichTextBox control is capable of managing tab positions on a paragraph-by-paragraph basis. This is achieved using the two properties SelTabCount and SelTabs: The former sets the number of tab positions in the paragraphs included in the selection, and the latter sets each tab position to a given value. Here's a simple example that shows how you can use these properties:

' Add three tabs, at 300, 600, and 1200 twips from left margin.
RichTextBox1.SelTabCount = 3
' The SelTabs property is zero-based.
' Tabs must be specified in increasing order, otherwise they are ignored.
RichTextBox1.SelTabs(0) = 300
RichTextBox1.SelTabs(1) = 600
RichTextBox1.SelTabs(2) = 1200

You can also read these properties to find the tab positions in selected paragraphs. Remember to account for Null values when the selection includes more paragraphs.

Here's one more issue that you should consider when working with tabs: The Tab key inserts a tab character only if there aren't any controls on the form whose TabStop property is set to True. In all other cases, the only way to insert a tab character in the document is by using the Ctrl+Tab key combination.

An easy way to work around this problem is to set the TabStop properties of all the controls to False when the focus enters the RichTextBox control and to reset them to True when the focus moves away from it. (Focus can move only when the user presses the hot key associated with another control or clicks on another control.) Here's a reusable routine that performs both jobs.

' In a BAS module
Sub SetTabStops(frm As Form, value As Boolean)
    Dim ctrl As Control
    On Error Resume Next
    For Each ctrl In frm.Controls
        ctrl.TabStop = value
    Next
End Sub

' In the form module that contains the RichTextBox control
Private Sub RichTextBox1_GotFocus()
    SetTabStops Me, False
End Sub
Private Sub RichTextBox1_LostFocus()
    SetTabStops Me, True
End Sub

Searching and replacing text

You can search text in a RichTextBox control by applying the InStr function to the value returned by the Text property. This control also supports the Find method, which makes the process even simpler and faster. The Find method has the following syntax:

pos = Find(Search, [Start], [End], [Options])

Search is the string being searched. Start is the index of the character from which the search should start (the index of first character is zero). End is the index of the character where the search should end. Options is one or more of the following constants: 2-rtfWholeWord, 4-rtfMatchCase, and 8-rtfNoHighlight. If the search is successful, the Find method highlights the matching text and returns its position; if the search fails, it returns -1. The matching string is highlighted even if the HideSelection property is True, and the control doesn't have the focus unless you specify the rtfNoHighlight flag.

If you omit the Start argument, the search starts from the current caret position and ends at the position indicated by the End argument. If you omit the End argument, the search starts from the position indicated by the Start argument and ends at the end of the document. If you omit both the Start and End arguments, the search is performed in the current selection (if there's selected text) or in the entire contents.

Implementing a Search and Replace function is simple. Because the Find method highlights the found string, all you have to do to replace it is assign a new value to the SelText property. You can also easily write a routine that replaces all the occurrences of a substring and returns the number of replacements:

Function RTFBoxReplace(rtb As RichTextBox, search As String, _
    replace As String, Optional options As FindConstants) As Long
    Dim count As Long, pos As Long
    Do
        ' Search the next occurrence.
        ' (Ensure that the rtfNoHighlight bit is off.)
        pos = rtb.Find(search, pos, , options And Not rtfNoHighlight)
        If pos = -1 Then Exit Do
        count = count + 1
        ' Replace the found substring.
        rtb.SelText = replace
        pos = pos + Len(replace)
    Loop
    ' Return the number of occurrences that have been replaced.
    RTFBoxReplace = count
End Function

The RTFBoxReplace routine is considerably slower than a plain VBA Replace function, but it preserves the original attributes of the replaced string.

Moving the caret and selecting text

The Span method extends the selection toward the start or the end of the document until a given character is found. Its syntax is the following:

Span CharTable, [Forward], [Negate]

CharTable is a string that contains one or more characters. Forward is the direction of the movement (True to move forward, False to move backward). Negate indicates where the movement terminates: If False (the default setting), it ends with the first character that doesn't belong to CharTable (and therefore the selection contains only characters that appear in CharTable). If True, the movement ends when any character contained in CharTable is encountered. (In this case, the selection contains only characters that don't appear in CharTable.) The Span method is useful for programmatically selecting a word or an entire sentence:

' Select from the caret to the end of the sentence.
' You need the CRLF to account for the paragraph's end.
RichTextBox1.Span " .,;:!?" & vbCrLf, True, True

To move the insertion point without selecting the text, you can use the UpTo method, which has the same syntax as Span:

' Move the caret to the end of the current sentence.
RichTextBox1.UpTo " .,;:!?" & vbCrLf, True, True

Another method that you might find useful is GetLineFromChar, which returns the line number that corresponds to a given offset from the beginning of the text. For example, you can use this method to display the number of the line on which the caret is currently located:

Private Sub RichTextBox1_SelChange()
    ' The return value from GetLineFromChar is zero-based.
    lblStatus.Caption = "Line " & (1 + RichTextBox1.GetLineFromChar _
        (RichTextBox1.SelStart))
End Sub

You can find out how many lines are in the current document by executing the following statement:

MsgBox (1 + RichTextBox1.GetLineFromChar(Len(RichTextBox1.Text))) _
    & " Lines"

Printing the current document

The RichTextBox control directly supports printing through the SelPrint method, which prints the current selection or the entire document if no text is selected. Its syntax is the following:

SelPrint hDC, [StartDoc]

hDC is the device context of the target printer, and StartDoc is a Boolean value that determines whether the method also sends StartDoc and EndDoc commands to the printer. The latter argument has been introduced with Visual Basic 6, and it's useful when you're working with printers that don't behave in the standard way. You can print the entire document on the current printer with just two statements:

RichTextBox1.SelLength = 0           ' Clear selection, if any.
RichTextBox1.SelPrint Printer.hDC    ' Send to the current printer.

A drawback of the SelPrint method is that you don't have any control over print margins. The demonstration program included on the companion CD shows how you can overcome this limit by using a technique based on Windows API calls.

Embedding objects

An intriguing feature of the RichTextBox control is its ability to embed OLE objects, which is similar to what you can do with the intrinsic OLE control. (The OLE control is briefly described in Chapter 3.) You exploit this capacity by means of the OLEObjects collection, which holds 0 or more OLEObject items. Each OLEObject item corresponds to an OLE object that has been embedded in the document. You can programmatically embed a new OLE object through the OLEObject collection's Add method, which has the following syntax:

Add ([Index], [Key], [SourceDoc], [ClassName]) As OLEObject

Index is the position in the collection where the object will be inserted. Key is an alphabetical key that will uniquely identify the object in the collection. SourceDoc is the filename of the embedded document that will be copied into the RichTextBox control. (It can be omitted to insert a blank document.) ClassName is the class name of the embedded object. (ClassName can be omitted if SourceDoc is specified.) For example, you can embed a blank Microsoft Excel worksheet at the current caret position by executing this code:

' This new object is associated to the "Statistics" key.
Dim statObj As RichTextLib.OLEObject
Set statObj = RichTextBox1.OLEObjects.Add(, "Statistics", _
    , "Excel.Sheet")

As soon as you add an OLEObject, it becomes active and is ready for input. OLEObject items expose a few properties and methods that let you (partially) control them through code. For example, the DisplayType property determines whether the object should display its contents (0-rtfDisplayContent) or its icon (1-rtfDisplayIcon):

' Show the object just added as an icon.
statObj.DisplayType = rtfDisplayIcon

Each embedded object supports a number of actions, called verbs. You can retrieve the verbs supported by the embedded object by using the FetchVerbs and then querying the ObjectVerbs and ObjectVerbsCount properties:

' Print the list of supported verbs to the Debug window.
statObj.FetchVerbs
For i = 0 To statObj.ObjectVerbsCount _ 1
    ' These strings are printed as they might appear in a pop-up
    ' menu and can include an & character.
    Debug.Print Replace(statObj.ObjectVerbs(i), "&" , "")
Next

The list of supported verbs typically includes actions such as Edit or Open. You can execute one of these actions by using the DoVerb method, which accepts a verb name, an index in the ObjectVerbs property, or a negative value for common actions (-1vbOLEShow, -2vbOLEOpen, -3vbOLEHide, -4vbOLEUIActivate, -5vbOLEInPlaceActivate, -6vbOLEDiscardUndoState). You can determine whether a verb is available by testing the ObjectVerbsFlags property. For example, you can print the contents of an embedded object using this code:

Dim i As Integer
For i = 0 To statObj.ObjectVerbsCount _ 1
    ' Filter out "&" characters.
    If Replace(statObj.ObjectVerbs(i), "&" , "") = "Print" Then
        ' A "Print" verb has been found, check its current state.
        If statObj.ObjectVerbFlags(i) = vbOLEFlagEnabled Then
            ' If the verb is enabled, start the print job.
            statObj.DoVerb i
        End If
        Exit For
    End If
Next

For more information about this feature, see the Visual Basic documentation.